﻿using AutoMapper;
using ezCloud.Data;
using Itenso.TimePeriod;
using Nest;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Linq;
using System.Data.SqlTypes;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Hosting;

namespace ezCloud.Api.Common
{
    public class STAAHReservations
    {
        public STAAHReservationList reservations { get; set; }
    }

    public class STAAHReservationList
    {
        public List<STAAHReservation> reservation { get; set; }
    }

    public class STAAHReservationLog
    {
        public DateTime date { get; set; }
        public object data { get; set; }
        public object errors { get; set; }
    }

    public class STAAHReservation
    {
        public string booking_id { get; set; }
        public string channel_ref { get; set; }
        public DateTime booking_date { get; set; }
        public string currencycode { get; set; }
        public string booking_status { get; set; }
        public decimal totalprice { get; set; }
        public string hotel_id { get; set; }
        public string hotel_name { get; set; }
        public List<STAAHRoom> room { get; set; }
        public STAAHCustomer customer { get; set; }
        public string company { get; set; }
        public decimal commissionamount { get; set; }
        public decimal deposit { get; set; }
    }

    public class STAAHRoom
    {
        public DateTime arrival_date { get; set; }
        public DateTime departure_date { get; set; }
        public int id { get; set; }
        public string name { get; set; }

        public List<STAAHPrice> price { get; set; }
        public decimal totalprice { get; set; }
        public string remarks { get; set; }
        public string guest_name { get; set; }
        public int numberofguests { get; set; }
        public int numberofadult { get; set; }
        public int numberofchild { get; set; }
    }

    public class STAAHPrice
    {
        public DateTime date { get; set; }
        public int rate_id { get; set; }
        public decimal amount { get; set; }
    }

    public class STAAHCustomer
    {
        public string first_name { get; set; }
        public string last_name { get; set; }
        public string address { get; set; }
        public string city { get; set; }
        public string countrycode { get; set; }
        public string email { get; set; }
        public string telephone { get; set; }
        public string remarks { get; set; }
        public string zip { get; set; }
    }

    public class ChannelManagerBackgroundJob
    {
        private static object _locker = new object();

        public static async Task PrepareForGettingFromChannelManager(string subUrl, HttpResponseMessage response)
        {
            using (var client = new HttpClient())
            {
                //STAAHReservationsUtility.IndexingInventory(data);
                client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ChannelManagerUrl"]);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
                response = await client.GetAsync(subUrl);
            }
        }

        public static async Task<bool> PrepareForPostingToChannelManager(object data, bool status)
        {
            using (var client = new HttpClient())
            {
                //STAAHReservationsUtility.IndexingInventory(data);
                client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ChannelManagerUrl"]);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
                try
                {
                    HttpResponseMessage response = await client.PostAsJsonAsync("set_availability/", data);
                    //var json = await response.Content.ReadAsStringAsync();
                    if (response.IsSuccessStatusCode)
                    {
                        status = true;
                        //STAAHReservationsUtility.IndexingInventory(data);
                        System.Diagnostics.Debug.WriteLine("Posting To Channel Manager Successful");
                    }
                    else
                    {
                        status = false;
                    }
                }
                catch (Exception ex)
                {
                    status = false;

                    //EmailService emailService = new EmailService();
                    //IdentityMessage message = new IdentityMessage();
                    //message.Destination = "hotro@ezcloud.vn";
                    //message.Subject = "ezCloud Hotel - Connector if off";
                    //message.Body = "Connecto is off";
                    //string emailTemplate = File.ReadAllText(HttpContext.Current.Server.MapPath("/EmailTemplates/emailCMError.html"));
                    //emailTemplate = emailTemplate.Replace("{{Fullname}}", "Nguyen Ngoc Dung");
                    //emailService.SendGmailAsync(message);
                }
                return status;
            }
        }

        public class Inventory
        {
            public string start_date { get; set; }
            public string end_date { get; set; }
            public string roomstosell { get; set; }
        }

        public class InventoryLog
        {
            public DateTime date { get; set; }
            public object inventory { get; set; }
            public string error { get; set; }
            public string roomType { get; set; }
            public string user { get; set; }
        }

        public static async Task ChannelManagerAutoBackgroundJobScheduler()
        {
            var context = new ezCloudDataContext();
            //var user = UserUtitily.getUserProfile();

            //            lock (_locker)
            {
                var cmBackgroundJobs = context.CMBackgroundJobs.Where(job => job.ActionStatus != null && job.ActionStatus == 0 && (job.LastUpdatedDate == null || ((DateTime)job.LastUpdatedDate).AddMinutes(job.Hotels.TimeZoneOffset) < DateTime.UtcNow.AddMinutes(job.Hotels.TimeZoneOffset).Date)).ToList()
                .GroupBy(gb => new { gb.Hotels, gb.CMRoomTypeMappings })
                .Select(s => new
                {
                    Hotels = s.Key.Hotels,
                    CMRoomTypeMappings = s.Key.CMRoomTypeMappings,
                    Inventories = s.ToList()
                }).ToList();

                if (cmBackgroundJobs != null)
                {
                    foreach (var bgJob in cmBackgroundJobs)
                    {
                        var hotel = bgJob.Hotels;
                        if (hotel != null)
                        {
                            var cmRoomTypeMappings = bgJob.CMRoomTypeMappings;
                            var inventories = new List<Inventory>();
                            var invTemp = bgJob.Inventories.OrderByDescending(o => o.CreatedDate).GroupBy(g => g.StartDate);
                            foreach (var item in invTemp)
                            {
                                var inv = item.OrderByDescending(i => i.CreatedDate).FirstOrDefault();
                                var inventoryTemp = new Inventory()
                                {
                                    start_date = String.Format("{0:yyyy-MM-dd}", inv.StartDate.Value.AddMinutes(hotel.TimeZoneOffset)),
                                    end_date = String.Format("{0:yyyy-MM-dd}", inv.EndDate.Value.AddMinutes(hotel.TimeZoneOffset)),
                                    roomstosell = inv.ActionValue
                                };
                                inventories.Add(inventoryTemp);
                            }

                            if (inventories.Count > 0)
                            {
                                var postData = new
                                {
                                    hotel_id = hotel.HotelId,
                                    room = new
                                    {
                                        room_id = cmRoomTypeMappings.CMRoomTypeId.ToString(),
                                        inventory = new
                                        {
                                            dates = inventories
                                        }
                                    }
                                };

                                foreach (var item in bgJob.Inventories)
                                {
                                    item.ActionStatus = -1;
                                    item.ActionUpdatedDate = DateTime.UtcNow;
                                }

                                context.SubmitChanges();

                                var bgUser = context.AspNetUsers.FirstOrDefault(usr => usr.Id == bgJob.Inventories[0].CreatedUserId);
                                var bgUserName = bgUser != null ? bgUser.Email : null;

                                //PrepareForPostingToChannelManager(postData, status);
                                bool status = await PrepareForPostingToChannelManager(postData, true);
                                var inventoryLog = new InventoryLog()
                                {
                                    date = DateTime.UtcNow,
                                    inventory = postData,
                                    roomType = cmRoomTypeMappings.RoomTypes.RoomTypeName,
                                    user = bgUserName,
                                    error = null
                                };
                                if (status != null)
                                {
                                    if (status == true)
                                    {
                                        foreach (var item in bgJob.Inventories)
                                        {
                                            item.ActionStatus = 1;
                                            item.ActionUpdatedDate = DateTime.UtcNow;
                                        }
                                        context.SubmitChanges();
                                        STAAHReservationsUtility.IndexingInventory(inventoryLog);
                                    }
                                    else
                                    {
                                        inventoryLog.error = "Can not connect to Channel Manager";
                                        STAAHReservationsUtility.IndexingInventory(inventoryLog);
                                        string listHeaderVN = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_HeaderVN.html"));
                                        string listHeaderEN = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_HeaderEN.html"));
                                        string subjec = "ezCloudHotel.com - Errors in the synchronization process / Thông báo lỗi đồng bộ phòng lên kênh bán phòng: " + hotel.HotelName.ToString() + "(Hotel ID: " + hotel.HotelId.ToString() + ")";
                                        string listInventoryErrorVN = listHeaderVN;
                                        string listInventoryErrorEN = listHeaderEN;
                                        foreach (var item in postData.room.inventory.dates)
                                        {
                                            string body = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_Rows.html"));
                                            body = body.Replace("{{time}}", inventoryLog.date.ToString("dd/MM/yyyy HH:mm"));
                                            body = body.Replace("{{usename}}", inventoryLog.user);
                                            body = body.Replace("{{startdate}}", item.start_date);
                                            body = body.Replace("{{enddate}}", item.end_date);
                                            body = body.Replace("{{roomtype}}", inventoryLog.roomType);
                                            body = body.Replace("{{sold}}", item.roomstosell);
                                            listInventoryErrorVN += body;
                                            listInventoryErrorEN += body;
                                        }
                                        string footer = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_Footer.html"));
                                        listInventoryErrorVN += footer;
                                        listInventoryErrorEN += footer;

                                        var currentUser = context.AspNetUsers.FirstOrDefault(usr => usr.Id == hotel.HotelOwnerId);
                                        EmailSender.SendEmailIfCMProcessInventoryFalse(currentUser.Email, subjec, currentUser.Email, hotel.HotelId, hotel.HotelName, listInventoryErrorVN, listInventoryErrorEN);
                                    }
                                }
                                else
                                {
                                    inventoryLog.error = "Can not connect to Channel Manager";
                                    STAAHReservationsUtility.IndexingInventory(inventoryLog);

                                    string listHeaderVN = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_HeaderVN.html"));
                                    string listHeaderEN = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_HeaderEN.html"));
                                    string subjec = "ezCloudHotel.com - Errors in the synchronization process / Thông báo lỗi đồng bộ phòng lên kênh bán phòng: " + hotel.HotelName.ToString() + "(Hotel ID: " + hotel.HotelId.ToString() + ")";
                                    string listInventoryErrorVN = listHeaderVN;
                                    string listInventoryErrorEN = listHeaderEN;
                                    foreach (var item in postData.room.inventory.dates)
                                    {
                                        string body = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_Rows.html"));
                                        body = body.Replace("{{time}}", inventoryLog.date.ToString("dd/MM/yyyy HH:mm"));
                                        body = body.Replace("{{usename}}", inventoryLog.user);
                                        body = body.Replace("{{startdate}}", item.start_date);
                                        body = body.Replace("{{enddate}}", item.end_date);
                                        body = body.Replace("{{roomtype}}", inventoryLog.roomType);
                                        body = body.Replace("{{sold}}", item.roomstosell);
                                        listInventoryErrorVN += body;
                                        listInventoryErrorEN += body;
                                    }
                                    string footer = File.ReadAllText(System.Web.Hosting.HostingEnvironment.MapPath("/EmailTemplates/emailCMError_Footer.html"));
                                    listInventoryErrorVN += footer;
                                    listInventoryErrorEN += footer;

                                    var currentUser = context.AspNetUsers.FirstOrDefault(usr => usr.Id == hotel.HotelOwnerId);
                                    EmailSender.SendEmailIfCMProcessInventoryFalse(currentUser.Email, subjec, currentUser.Email, hotel.HotelId, hotel.HotelName, listInventoryErrorVN, listInventoryErrorEN);
                                }
                            }
                        }
                    }
                }
            }
        }

        public static async Task<bool> ChannelManagerBackgroundJobScheduler()
        {
            bool isSendRoomAvailabletoCM = true;
            var context = new ezCloudDataContext();
            var user = UserUtitily.getUserProfile();

            {
                var cmBackgroundJobs = context.CMBackgroundJobs.Where(job => job.ActionStatus != null && job.ActionStatus == 0 && (job.LastUpdatedDate == null || ((DateTime)job.LastUpdatedDate).AddMinutes(job.Hotels.TimeZoneOffset) < DateTime.UtcNow.AddMinutes(job.Hotels.TimeZoneOffset).Date)).ToList()
                .GroupBy(gb => new { gb.Hotels, gb.CMRoomTypeMappings })
                .Select(s => new
                {
                    Hotels = s.Key.Hotels,
                    CMRoomTypeMappings = s.Key.CMRoomTypeMappings,
                    Inventories = s.ToList()
                }).ToList();

                if (cmBackgroundJobs != null)
                {
                    foreach (var bgJob in cmBackgroundJobs)
                    {
                        var hotel = bgJob.Hotels;
                        if (hotel != null)
                        {
                            var cmRoomTypeMappings = bgJob.CMRoomTypeMappings;
                            var inventories = new List<Inventory>();
                            foreach (var item in bgJob.Inventories)
                            {
                                var inventoryTemp = new Inventory()
                                {
                                    start_date = String.Format("{0:yyyy-MM-dd}", item.StartDate.Value.AddMinutes(hotel.TimeZoneOffset)),
                                    end_date = String.Format("{0:yyyy-MM-dd}", item.EndDate.Value.AddMinutes(hotel.TimeZoneOffset)),
                                    roomstosell = item.ActionValue
                                };
                                inventories.Add(inventoryTemp);
                            }

                            if (inventories.Count > 0)
                            {
                                var postData = new
                                {
                                    hotel_id = hotel.HotelId,
                                    room = new
                                    {
                                        room_id = cmRoomTypeMappings.CMRoomTypeId.ToString(),
                                        inventory = new
                                        {
                                            dates = inventories
                                        }
                                    }
                                };

                                foreach (var item in bgJob.Inventories)
                                {
                                    item.ActionStatus = -1;
                                }

                                context.SubmitChanges();

                                var bgUser = context.AspNetUsers.FirstOrDefault(usr => usr.Id == bgJob.Inventories[0].CreatedUserId);
                                var bgUserName = bgUser != null ? bgUser.Email : null;
                                bool status = await PrepareForPostingToChannelManager(postData, true);

                                var inventoryLog = new InventoryLog()
                                {
                                    date = DateTime.UtcNow,
                                    inventory = postData,
                                    roomType = cmRoomTypeMappings.RoomTypes.RoomTypeName,
                                    user = bgUserName,
                                    error = null
                                };
                                if (status == true)
                                {
                                    foreach (var item in bgJob.Inventories)
                                    {
                                        item.ActionStatus = 1;
                                    }
                                    context.SubmitChanges();
                                }
                                else
                                {
                                    isSendRoomAvailabletoCM = false;
                                    inventoryLog.error = "Can not connect to Channel Manager";
                                }
                                STAAHReservationsUtility.IndexingInventory(inventoryLog);
                            }
                        }
                    }
                }
            }
            return isSendRoomAvailabletoCM;
        }
    }

    public static class STAAHReservationsUtility
    {
        public static async Task PrepareForGettingFromChannelManager(string subUrl, HttpResponseMessage response)
        {
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(ConfigurationManager.AppSettings["ChannelManagerUrl"]);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
                response = await client.GetAsync(subUrl);
            }
        }

        public static async Task IndexingInventory(object inventoryData)
        {
            var node = new Uri("http://localhost:9200");
            var defaultIndex = ConfigurationSettings.AppSettings["elasticIndex"];
            HostingEnvironment.QueueBackgroundWorkItem(t =>
            {
                try
                {
                    var settings = new ConnectionSettings(
                            node,
                            defaultIndex: defaultIndex
                        );
                    var client = new ElasticClient(settings);

                    var result = client.Index(inventoryData, i => i.Index(defaultIndex).Type("CMInventory").Id(Guid.NewGuid().ToString()).Refresh());
                }
                catch (Exception ex) { }
            });
            return;
        }

        public static async Task IndexingSTAAHReservation(STAAHReservationDataIndexing data)
        {
            var user = Common.UserUtitily.getUserProfile();
            var node = new Uri("http://localhost:9200");
            var defaultIndex = ConfigurationSettings.AppSettings["elasticIndex"];
            HostingEnvironment.QueueBackgroundWorkItem(t =>
            {
                try
                {
                    var settings = new ConnectionSettings(
                            node,
                            defaultIndex: defaultIndex
                        );
                    var client = new ElasticClient(settings);

                    var result = client.Index(data, i => i.Index(defaultIndex).Type("STAAHReservation").Id(Guid.NewGuid().ToString()).Refresh());
                }
                catch (Exception ex) { }
            });
            return;
        }

        public class STAAHReservationDataIndexing
        {
            public DateTime date { get; set; }
            public STAAHReservations data { get; set; }
            public List<String> errors { get; set; }
        }

        public static void AddNewSTAAHReservation(string action, STAAHReservations data, STAAHReservation item, ezCloudDataContext context, Hotels currentHotel, List<Money> currencies, List<CMRoomTypeMappings> cmRoomTypeMappings, List<CMRoomRateMappings> cmRoomRateMappings, Money defaultCurrency, CMConfigurations cmConfiguration, List<Countries> listCountry)
        {
            try
            {
                var HotelId = currentHotel.HotelId;
                var res = new Reservations();
                var cmCurrency = currencies.FirstOrDefault(c => c.HotelId == HotelId && c.MoneyName.ToLower() == item.currencycode.ToLower());
                var currencyIssnull = false;
                string currencyIssnullErrorDescription = null;
                if (cmCurrency == null)
                {
                    currencyIssnull = true;
                    currencyIssnullErrorDescription += "System does not support the currency [" + item.currencycode + "]";
                    cmCurrency = defaultCurrency;
                    res.CMConfirmed = false;
                }

                var staahReservationData = new STAAHReservationDataIndexing();
                staahReservationData.date = DateTime.UtcNow;
                staahReservationData.data = data;
                staahReservationData.errors = new List<String>();
                if (action == "new")
                {
                    if (cmCurrency == null)
                    {
                        res.CMConfirmed = false;
                    }
                    res.HotelId = HotelId;
                    res.CreatedDate = DateTime.UtcNow;
                    res.CreatedUserId = currentHotel.HotelOwnerId;
                    res.ReservationNumber = InvoiceNumberGenerator.getInvoiceNumber(HotelId, "RESERVATION");
                    res.CMBookingId = item.booking_id;
                    res.CMChannelRef = item.channel_ref;
                    res.Color = "#43A047";
                    if (item.room.Count > 1) res.IsGroup = true;
                    else res.IsGroup = false;
                    // Source
                    res.Source = cmConfiguration.Source;
                    // Company
                    if (item.company != "")
                    {
                        var company = context.Company.FirstOrDefault(c => c.HotelId == HotelId && c.CompanyCode == item.company.Trim());
                        if (company == null)
                        {
                            var newCompany = new Company();
                            newCompany.CompanyCode = item.company.Trim();
                            newCompany.CompanyName = item.company.Trim();
                            newCompany.IsAvailable = true;
                            newCompany.CreatedDate = DateTime.UtcNow;
                            newCompany.CreatedUserId = currentHotel.HotelOwnerId;
                            newCompany.HotelId = HotelId;
                            context.Company.InsertOnSubmit(newCompany);
                            res.Company = newCompany;
                        }
                        else
                        {
                            res.Company = company;
                        }
                    }
                    // Market
                    res.Market = null;
                    // Commission
                    if (item.commissionamount > 0)
                    {
                        res.CommissionFlat = item.commissionamount;
                    }

                    context.Reservations.InsertOnSubmit(res);
                }
                else
                {
                    res = context.Reservations.FirstOrDefault(r => r.HotelId == HotelId && r.CMBookingId == item.booking_id && r.CMChannelRef == item.channel_ref);
                    if (res != null)
                    {
                        // Source
                        res.Source = cmConfiguration.Source;
                        // Company
                        if (item.company != "")
                        {
                            var company = context.Company.FirstOrDefault(c => c.HotelId == HotelId && c.CompanyCode == item.company.Trim());
                            res.Company = company;
                        }
                        // Market
                        res.Market = null;
                        // Commission
                        if (item.commissionamount > 0)
                        {
                            res.CommissionFlat = item.commissionamount;
                        }
                    }
                }

                if (res == null)
                {
                    //send Email
                    string subject = "Error process CM RES is null:" + DateTime.UtcNow.AddHours(7) + ", HotelId:" + item.hotel_id + ", booking id:" + item.booking_id;
                    string body = "";

                    EmailSender.SendEmail("dung.nguyen@ezcloud.vn", subject, body);
                    string email = ConfigurationSettings.AppSettings["emailHotelReservationsFalse"];
                    EmailSender.SendEmail(email, subject, body);

                    return;
                }

                //Customer
                var traveller = new Travellers();
                if (item.customer.countrycode.Trim().Length > 0)
                {
                    //Countries country = listCountry.FirstOrDefault(c => c.CountryName.ToLower() == item.customer.countrycode.Trim().ToLower());
                    Countries country = listCountry.FirstOrDefault(c => c.CountryCode.ToLower() == item.customer.countrycode.Trim().ToLower());
                    if (country == null) country = listCountry.FirstOrDefault(c => c.CountryCode.ToLower() == "vn");

                    //traveller.Countries = listCountry.FirstOrDefault(c => c.CountryName.ToLower() == item.customer.countrycode.Trim().ToLower());
                    traveller.Countries = country;
                }
                traveller.Fullname = Utilities.GetFullName(item.customer.first_name.Trim(), item.customer.last_name.Trim()) ;
                traveller.Address = item.customer.address + "," + item.customer.city;
                traveller.Email = item.customer.email;
                traveller.Mobile = item.customer.telephone;
                traveller.Note = item.customer.remarks + ", zip:" + item.customer.zip;
                traveller.HotelId = HotelId;
                traveller.Gender = 2;
                traveller.Birthday = default(DateTime);
                traveller.CreatedDate = DateTime.UtcNow;
                traveller.CreatedUserId = currentHotel.HotelOwnerId;
                context.Travellers.InsertOnSubmit(traveller);
                var flagDeposit = true;
                var flagIsGroupMaster = true;
                List<ReservationRooms> listRR = new List<ReservationRooms>();
                foreach (var itemR in item.room)
                {
                    DateTime arrivalDate = Convert.ToDateTime(itemR.arrival_date);
                    DateTime departureDate = Convert.ToDateTime(itemR.departure_date);
                    // if hotel use option IsUseTimeInOutPrivate, need to add default time in and time out for each booking
                    if (currentHotel.IsUseTimeInOutPrivate != null && (bool)currentHotel.IsUseTimeInOutPrivate)
                    {
                        //if (currentHotel.TimeInPrivate!=null)

                        arrivalDate = arrivalDate + currentHotel.TimeInPrivate.Value;

                        //if (currentHotel.TimeOutPrivate != null)
                        departureDate = departureDate + currentHotel.TimeOutPrivate.Value;
                    }
                    ReservationRooms RR = new ReservationRooms();
                    // set first room is Group Master
                    RR.IsGroupMaster = false;
                    if (flagIsGroupMaster && item.room.Count > 1) RR.IsGroupMaster = true;

                    //set default CMConfirmed is true for all reservationroom
                    RR.CMConfirmed = true;
                    RR.CMErrorDescription = null;
                    if (currencyIssnull)
                    {
                        RR.CMConfirmed = false;
                        if (RR.CMErrorDescription != null)
                            RR.CMErrorDescription += ", " + currencyIssnullErrorDescription;
                        else
                            RR.CMErrorDescription = currencyIssnullErrorDescription;

                        staahReservationData.errors.Add(RR.CMErrorDescription);
                    }
                    var cmRoomType = cmRoomTypeMappings.FirstOrDefault(c => c.CMRoomTypeId == itemR.id && c.IsDeleted == false);
                    if (cmRoomType != null)
                    {
                        RR.RoomTypes = cmRoomType.RoomTypes;
                    }
                    else
                    {
                        RR.RoomTypes = currentHotel.RoomTypesList.FirstOrDefault(rt => rt.IsHidden == false);
                        res.CMConfirmed = false;
                        RR.CMConfirmed = false;
                        if (RR.CMErrorDescription != null)
                            RR.CMErrorDescription += ", Room type [" + itemR.name + "] is not mapped";
                        else
                            RR.CMErrorDescription = "Room type [" + itemR.name + "] is not mapped";

                        staahReservationData.errors.Add(RR.CMErrorDescription);
                    }

                    var rate_id = itemR.price[0].rate_id;

                    var cmRoomRate = cmRoomRateMappings.FirstOrDefault(c => c.CMRoomRateId == rate_id && c.RoomTypeId == RR.RoomTypes.RoomTypeId && c.IsDeleted == false);
                    if (cmRoomRate != null)
                    {
                        RR.RoomPrices = cmRoomRate.RoomPrices;
                    }
                    else
                    {
                        RR.RoomPrices = currentHotel.RoomPricesList.FirstOrDefault(rp => rp.IsActive == true && rp.RoomTypeId == RR.RoomTypes.RoomTypeId);
                        res.CMConfirmed = false;
                        RR.CMConfirmed = false;
                        if (RR.CMErrorDescription != null)
                            RR.CMErrorDescription += ", Room rate [" + rate_id + "] is not mapped";
                        else
                            RR.CMErrorDescription = "Room rate [" + rate_id + "] is not mapped";

                        staahReservationData.errors.Add(RR.CMErrorDescription);
                    }
                    RR.Reservations = res;
                    RR.HotelId = HotelId;
                    RR.CMReceiveBookingDate = DateTime.UtcNow;
                    RR.CreatedDate = Convert.ToDateTime(item.booking_date).AddMinutes(-currentHotel.TimeZoneOffset);
                    RR.CreatedUserId = currentHotel.HotelOwnerId;
                    RR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber(HotelId, "RESERVATIONROOM");
                    RR.BookingStatus = "BOOKED";
                    RR.ArrivalDate = arrivalDate.AddMinutes(-currentHotel.TimeZoneOffset);
                    RR.DepartureDate = departureDate.AddMinutes(-currentHotel.TimeZoneOffset);
                    RR.Adults = itemR.numberofadult;
                    RR.Child = itemR.numberofchild;
                    RR.Foc = false;
                    RR.DiscountFlat = 0;
                    RR.DiscountPercentage = 0;
                    RR.Price = (decimal)RR.RoomPrices.FullDayPrice;
                    RR.Travellers = traveller;
                    RR.Note = itemR.remarks == "" ? null : itemR.remarks;
                    if (cmConfiguration != null && cmConfiguration.IsAutoAssignRoom == true)
                    {
                        RR.StatusAutoAssignRoom = 1; // not Process
                    }

                    if (currencyIssnull)
                    {
                        if (RR.Note != null)
                            RR.Note += ", " + currencyIssnullErrorDescription;
                        else
                            RR.Note = currencyIssnullErrorDescription;
                    }

                    // Breakfasts
                    var dateRange = new DateRange(RR.ArrivalDate.Value, RR.DepartureDate.Value);
                    List<DateTime> roomBreakfastDateList = ReservationUtility.GenerateEachDayBreakfast(dateRange, currentHotel);
                    foreach (DateTime day in roomBreakfastDateList)
                    {
                        RoomBreakfast roomBreakfast = new RoomBreakfast();
                        roomBreakfast.ReservationRooms = RR;
                        roomBreakfast.HotelId = HotelId;
                        roomBreakfast.CreatedDate = DateTime.UtcNow;
                        roomBreakfast.CreatedUserId = currentHotel.HotelOwnerId;
                        roomBreakfast.IsBreakfast = true;
                        roomBreakfast.UsedDate = day;
                        context.RoomBreakfast.InsertOnSubmit(roomBreakfast);
                    }

                    // Room Charges
                    //var fromTemp = dateRange.Start.AddMinutes(currentHotel.TimeZoneOffset).Date.AddMinutes(-currentHotel.TimeZoneOffset);
                    //var toTemp = dateRange.End.AddMinutes(currentHotel.TimeZoneOffset).Date.AddMinutes(-currentHotel.TimeZoneOffset);
                    List<DateTime> roomChargeDateList = ReservationUtility.GenerateEachDayRoomCharge(dateRange, currentHotel);
                    var roomChargeList = new List<RoomCharges>();
                    foreach (DateTime day in roomChargeDateList)
                    {
                        RoomCharges roomCharge = new RoomCharges()
                        {
                            ReservationRooms = RR,
                            HotelId = HotelId,
                            RoomId = RR.RoomId,
                            StartDate = day,
                            EndDate = day,
                            RoomTypeId = RR.RoomTypeId,
                            Amount = RR.Price,
                            RoomPriceId = RR.RoomPriceId
                        };
                        roomChargeList.Add(roomCharge);
                        context.RoomCharges.InsertOnSubmit(roomCharge);
                    }
                    foreach (var itemRP in itemR.price)
                    {
                        // Of course Amount in RoomCharge must be in default currency
                        foreach (var roomCharge in roomChargeList)
                        {
                            if (roomCharge.StartDate.Value.AddMinutes(currentHotel.TimeZoneOffset).Date == itemRP.date.Date)
                            {
                                roomCharge.Amount = itemRP.amount * (decimal)cmCurrency.ExchangeRate;
                                break;
                            }
                        }

                        // if CM Currency is not the same as Hotel Currency, we give it a note about this case
                        if (defaultCurrency != cmCurrency)
                        {
                            var paymentDescription = Math.Abs(Convert.ToDecimal(itemRP.amount * (decimal)cmCurrency.ExchangeRate)).ToString("c", Utilities.GetCurrencyFormatProviderSymbolDecimals(defaultCurrency.MoneyName));
                            if (RR.Note != null)
                                RR.Note += ";" + itemRP.date.Date.ToString("dd/MM/yyyy") + "-Price: " + cmCurrency.ExchangeRate.ToString() + "x" + itemRP.amount.ToString() + "=" + paymentDescription + " " + defaultCurrency.MoneyName;
                            else
                                RR.Note = itemRP.date.Date.ToString("dd/MM/yyyy") + "-Price: " + cmCurrency.ExchangeRate.ToString() + "x" + itemRP.amount.ToString() + "=" + paymentDescription + " " + defaultCurrency.MoneyName;
                        }
                    }

                    context.ReservationRooms.InsertOnSubmit(RR);

                    // Reservation Travellers
                    var rtTemp = new ReservationTravellers();
                    rtTemp.Travellers = traveller;
                    rtTemp.ReservationRooms = RR;
                    context.ReservationTravellers.InsertOnSubmit(rtTemp);

                    // Reservation Traveller Extra Information
                    var rtExtraInfo = new ReservationTravellerExtraInformation();
                    rtExtraInfo.HotelId = HotelId;
                    rtExtraInfo.ReservationTravellers = rtTemp;
                    rtExtraInfo.IsChild = false;
                    context.ReservationTravellerExtraInformation.InsertOnSubmit(rtExtraInfo);

                    // Add Deposit
                    if (item.deposit != 0)
                    {
                        if (flagDeposit)
                        {
                            var deposit = new Payments
                            {
                                HotelId = HotelId,
                                ReservationRooms = RR,
                                PaymentTypeName = "DEPOSIT",
                                Amount = item.deposit,
                                CreatedDate = DateTime.UtcNow,
                                CreatedUserId = currentHotel.HotelOwnerId,
                                //PaymentMethodId = 1,
                                PaymentMethodId = 3, //BANK_TRANSFER
                                Money = cmCurrency,
                                PaymentNumber = InvoiceNumberGenerator.getInvoiceNumber(HotelId, "PAYMENT")
                            };

                            if (currencyIssnull)
                            {
                                if (deposit.PaymentDescription != null)
                                    deposit.PaymentDescription += ", " + currencyIssnullErrorDescription;
                                else
                                    deposit.PaymentDescription = currencyIssnullErrorDescription;
                            }
                            // We check it again if CM Currency is not the same as Hotel Currency
                            if (defaultCurrency != cmCurrency)
                            {
                                var paymentDescription = Math.Abs(Convert.ToDecimal(item.deposit * (decimal)cmCurrency.ExchangeRate)).ToString("c", Utilities.GetCurrencyFormatProviderSymbolDecimals(defaultCurrency.MoneyName));
                                if (deposit.PaymentDescription != null)
                                    deposit.PaymentDescription += "; " + DateTime.UtcNow.Date.ToString("dd/MM/yyyy") + ", Price: " + cmCurrency.ExchangeRate.ToString() + "x" + item.deposit.ToString() + "=" + paymentDescription + " " + defaultCurrency.MoneyName;
                                else
                                    deposit.PaymentDescription = DateTime.UtcNow.Date.ToString("dd/MM/yyyy") + ", Price: " + cmCurrency.ExchangeRate.ToString() + "x" + item.deposit.ToString() + "=" + paymentDescription + " " + defaultCurrency.MoneyName;
                                deposit.Amount = item.deposit * (decimal)cmCurrency.ExchangeRate;
                                deposit.AmountInSpecificMoney = item.deposit;
                            }
                            context.Payments.InsertOnSubmit(deposit);

                            //Send a notification to hotel owner
                            var currentClientUser = context.AspNetUsers.Where(a => a.Id == currentHotel.HotelOwnerId).FirstOrDefault();
                            if (currentClientUser != null)
                            {
                                var spaymentAmount = Math.Abs(Convert.ToDecimal(deposit.Amount)).ToString("c", Utilities.GetCurrencyFormatProviderSymbolDecimals(cmCurrency.MoneyName));
                                var paymentMessage = "Booking Id " + res.CMBookingId + " deposits " + cmCurrency.MoneyName + " " + spaymentAmount;
                                Notification.sendNotificationToDevice(currentClientUser.UserName.ToLower(), paymentMessage, currentHotel.HotelId, "NOTIFICATION_WHEN_POST_PAYMENT");
                            }

                            flagDeposit = false;
                        }
                    }

                    flagIsGroupMaster = false;
                    listRR.Add(RR);
                }
                var currentClientUser2 = context.AspNetUsers.Where(a => a.Id == currentHotel.HotelOwnerId).FirstOrDefault();
                string notifi = "There are new reservations from " + item.company.Trim() + " channels, booking code " + res.ReservationNumber + ", Booking Id: " + item.booking_id + " (CMChannelRef), number of rooms: " + item.room.Count();
                Notification.processNotificationBookedCm(currentClientUser2.UserName.ToLower(), notifi, currentHotel.HotelId);
                context.SubmitChanges();

                // update Available room on Matrix and send available room to CM
                ReservationUtility.RebuildAvailableRoomOnMatrix("new", listRR, currentHotel, context);
                //if (cmConfiguration != null && cmConfiguration.IsAutoAssignRoom == true)
                //{
                //    foreach (var RR in listRR)
                //    {
                //        // Auto Assign Room
                //        CMAutoAssignRoom(RR, context);
                //    }
                //}

                // Indexing STAAH Reservation Data
                STAAHReservationsUtility.IndexingSTAAHReservation(staahReservationData);


                //Elastic.IndexLastAudit(context);

            }
            catch (Exception ex)
            {

                string subject = "AddNewSTAAHReservation =>Error process CM:" + DateTime.UtcNow.AddHours(7)+", HotelId:"+item.hotel_id+", booking id:"+item.booking_id ;
                string body ="Message:" + ex.Message + ",Source:" + ex.Source + ";StackTrace:" + ex.StackTrace;

                EmailSender.SendEmail("dung.nguyen@ezcloud.vn", subject, body);
                string email = ConfigurationSettings.AppSettings["emailHotelReservationsFalse"];
                EmailSender.SendEmail(email, subject, body);
            }


            
        }

        public static async Task CancelSTAAHReservation(STAAHReservations data, STAAHReservation item, ezCloudDataContext context, Hotels currentHotel)
        {
            var HotelId = currentHotel.HotelId;
            var staahReservationData = new STAAHReservationDataIndexing();
            staahReservationData.date = DateTime.UtcNow;
            staahReservationData.data = data;
            staahReservationData.errors = new List<String>();
            var currentReservation = context.Reservations.FirstOrDefault(res => res.HotelId == HotelId && res.CMBookingId == item.booking_id && res.CMChannelRef == item.channel_ref);
            if (currentReservation == null)
            {
                // send email
                //send an email to hotel owner and administrator

                //EmailService emailService = new EmailService();
                //IdentityMessage message = new IdentityMessage();
                //message.Destination = "hotro@ezcloud.vn";
                //message.Subject = "Error: Hotel id " + hotel_id + " is not mapped";
                //message.Body = "ezCloud Hotel - Hotel id " + hotel_id + " is not mapped, data received from STAAH: " + STAAHData.ToString();
                //string emailTemplate = File.ReadAllText(HttpContext.Current.Server.MapPath("/EmailTemplates/emailCMError.html"));
                //emailService.SendGmailAsync(message);

                staahReservationData.errors.Add("Booking " + item.booking_id + " is not found");
                return;
            }

            var RRList = currentReservation.ReservationRoomsList;
            foreach (var RR in currentReservation.ReservationRoomsList)
            {
                RR.BookingStatus = "CANCELLED";
                RR.CancelDate = DateTime.UtcNow;
                RR.CancelUserId = currentHotel.HotelOwnerId;
                RR.CancelReason = "This reservation (RRID = " + RR.ReservationRoomId + ") was cancelled automatically";

                //If have deposit need to delete
                var listPayment = context.Payments.Where(p => p.HotelId == currentHotel.HotelId && p.ReservationRoomId == RR.ReservationRoomId && p.PaymentTypeName == "DEPOSIT").ToList();
                if (listPayment != null)
                {
                    foreach (var payment in listPayment)
                    {
                        Payments oldPayment = payment;

                        Payments deletedPayment = new Payments();

                        deletedPayment.PaymentDescription = payment.PaymentDescription;
                        deletedPayment.HotelId = payment.HotelId;
                        deletedPayment.ReservationRoomId = payment.ReservationRoomId;
                        deletedPayment.PaymentTypeName = "DELETED";
                        deletedPayment.Amount = -payment.Amount;
                        deletedPayment.CreatedDate = DateTime.UtcNow;
                        deletedPayment.CreatedUserId = currentHotel.HotelOwnerId;
                        deletedPayment.PaymentMethodId = payment.PaymentMethodId;
                        deletedPayment.MoneyId = payment.MoneyId;
                        deletedPayment.AmountInSpecificMoney = payment.AmountInSpecificMoney;
                        deletedPayment.FolioId = payment.FolioId;
                        deletedPayment.PaymentNumber = payment.PaymentNumber;
                        deletedPayment.RefPaymentId = payment.PaymentId;

                        context.Payments.InsertOnSubmit(deletedPayment);
                        context.SubmitChanges();
                        Common.Elastic.IndexLastAudit(context);
                        oldPayment.RefPaymentId = deletedPayment.PaymentId;
                        context.SubmitChanges();
                        Common.Elastic.IndexLastAudit(context);
                    }
                }
            }

            context.SubmitChanges();
            Elastic.IndexLastAudit(context);

            // update Available room on Matrix and send available room to CM
            ReservationUtility.RebuildAvailableRoomOnMatrix("cancel", RRList.ToList(), currentHotel, context);

            // Indexing STAAH Reservation Data
            STAAHReservationsUtility.IndexingSTAAHReservation(staahReservationData);

            var currentClientUser2 = context.AspNetUsers.Where(a => a.Id == currentHotel.HotelOwnerId).FirstOrDefault();
            string notifi = "Cancellation code " + currentReservation.ReservationNumber + " Booking Id " + currentReservation.CMBookingId + "(CMChannelRef) from the sale of the room channel " + currentReservation.Company;
            Notification.processNotificationBookedCm(currentClientUser2.UserName.ToLower(), notifi, currentHotel.HotelId);
        }

        /// <summary>
        /// Auto Assign Room Method for Reservation Room (RR) from CM
        /// * In the best case, RR is not fragmented, we simply assign appropriately room for it
        /// * In case RR was subdivided into other RRs, we have some rules below:
        ///     1. The maximum number of sub RRs is 4 and of cause the minimum number is 2
        ///     2. If the number of sub RRs excess 4, we have to send email to hotel owner to confirm this situation
        ///     3. If the number of sub RRs equal to 2, we have to reorder valid RRs in order to make the time range which we can put the input RR in it
        ///     4. Due to algorithm complexity and based on hotel actual operations, we do not make the problem becomes more complex such as:
        ///         - Recursion: not needed because we only need 2 loop to find max 4 available time range
        ///         - Breadth-first search, Depth-first search: a little bit, not as complicated as these definitions
        ///        Reason:
        ///         - We do not care about the nature of sub RRs: are these too short? are these more "beautiful" than others? (beautiful means: longer or balancer or etc...)
        ///         - We do not care how much sub RRs we would have to move to make space, just loop sequentially through all free time blocks
        ///         - ...
        /// * (From rule 3) Reorder Algorithm:
        ///     INPUT: Reservation Room from CM (RR)
        ///     1. Find all free time blocks between RR.ArrivalDate and RR.DepartureDate
        ///         - Free Time Block (FTB) means: all time range which is not contain any Reservation Room or Room Repair.
        ///         - Have to persist all these FTB corresponding with Room on some kind of model: Room A has (n) FTB.
        ///     2. Find all subsets of FTBs which satisfy conditions:
        ///         - Subset has capable of covering time range needed from RR
        ///         - Subset contains at least 2 element: one which Start point equals to RR.ArrivalDate and one which End point equals to RR.DepartureDate
        ///         - Subset does not contain any kind of 2 element which one element completely has inside other element
        ///     3. Loop through all subsets from step 2
        ///         -
        ///         -
        ///         -
        ///         -
        ///     4.
        /// </summary>
        /// <param name="RR"></param>
        /// <param name="context"></param>
        public static async Task CMAutoAssignRoom(int rrId)
        {
            using (var context = new ezCloudDataContext())
            {
                var RR = context.ReservationRooms.FirstOrDefault(rr => rr.ReservationRoomId == rrId);
                if (RR != null)
                {
                    var currentHotel = RR.Hotels;
                    if (currentHotel != null)
                    {
                        TimePeriodCollection combinedCMRRPeriods = new TimePeriodCollection();
                        combinedCMRRPeriods.Add(new TimeRange(RR.ArrivalDate.Value, RR.DepartureDate.Value));
                        var rooms = currentHotel.RoomsList.Where(rm => rm.HotelId == RR.HotelId && rm.RoomTypeId == RR.RoomTypeId && rm.IsDeleted == false && rm.IsHidden == false).ToList();
                        if (rooms != null && rooms.Count > 0)
                        {
                            var combinedHotelFreeTimeBlock = BuildCombinedHotelFreeTimeBlock(currentHotel, RR.ArrivalDate, RR.DepartureDate, RR.RoomTypeId, rooms);
                            if (combinedHotelFreeTimeBlock.singleRoomValid != null)
                            {
                                RR.RoomId = combinedHotelFreeTimeBlock.singleRoomValid.RoomId;
                                RR.ArrivalDate.Value.AddSeconds(1);
                                RR.DepartureDate.Value.AddSeconds(-1);
                                context.SubmitChanges();
                                return;
                            }
                            var validFreeTimeBlockSets = combinedHotelFreeTimeBlock.validFreeTimeBlockSets;
                            var roomFreeTimeBlocks = combinedHotelFreeTimeBlock.roomFreeTimeBlocks;

                            if (validFreeTimeBlockSets != null && validFreeTimeBlockSets.Count > 0)
                            {
                                var validFreeTimeBlockSets_Temp = validFreeTimeBlockSets.ConvertAll(set => set);
                                // First Time Searching
                                foreach (var set in validFreeTimeBlockSets)
                                {
                                    var firstBlock = set.FirstOrDefault(s => s.Start == RR.ArrivalDate.Value);
                                    var lastBlock = set.FirstOrDefault(s => s.End == RR.DepartureDate.Value);

                                    if (firstBlock.IntersectsWith(lastBlock))
                                    {
                                        validFreeTimeBlockSets_Temp.Remove(set);
                                        if (await SwapReservationRoomsToFreeTimeBlock(firstBlock, lastBlock, roomFreeTimeBlocks, rooms, RR, context).ConfigureAwait(false))
                                        {
                                            context.SubmitChanges();
                                            return;
                                        }
                                        else
                                        {
                                            continue;
                                        }
                                    }
                                }

                                // Second Time Searching
                                foreach (var set in validFreeTimeBlockSets_Temp)
                                {
                                    var firstBlock = set.FirstOrDefault(s => s.Start == RR.ArrivalDate.Value);
                                    var lastBlock = set.FirstOrDefault(s => s.End == RR.DepartureDate.Value);
                                    var second_combinedHotelFreeTimeBlock = BuildCombinedHotelFreeTimeBlock(currentHotel, firstBlock.End, lastBlock.Start, RR.RoomTypeId, rooms);
                                    if (await SplitReservationRooms(RR, firstBlock, lastBlock, roomFreeTimeBlocks, second_combinedHotelFreeTimeBlock, rooms, context).ConfigureAwait(false))
                                    {
                                        return;
                                    }
                                    else
                                    {
                                        continue;
                                    }
                                }
                            }

                            // Send Email
                            var currentUser = context.AspNetUsers.FirstOrDefault(usr => usr.Id == currentHotel.HotelOwnerId);
                            var guestName = currentHotel.TravellersList.Where(t => t.HotelId == currentHotel.HotelId && t.TravellerId == RR.TravellerId).FirstOrDefault().Fullname.ToString();
                            var CMChennelRef = RR.Reservations.CMChannelRef.ToString();
                            var companyName = RR.Reservations.Company.CompanyName.ToString();
                            string subjec = "Auto assign room unsuccessful / Thông báo về việc gán phòng tự động không thành công: " + currentHotel.HotelName.ToString() + "(Hotel ID: " + currentHotel.HotelId.ToString() + ")";
                            await EmailSender.SendEmailHotelAutoAssignRoomUnsuccessful(currentUser.Email, subjec, currentHotel.HotelEmail, CMChennelRef, companyName, guestName, (DateTime)RR.ArrivalDate, (DateTime)RR.DepartureDate, RR.RoomTypes.RoomTypeName);
                        }
                    }
                }
                
            }
            
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static async Task ChannelManagerAutoAssignRoomJobScheduler()
        {
            try
            {
                using (var context = new ezCloudDataContext())
                {
                    var listRRCM = context.ProcessAssignRoom().ToList();
                    if (listRRCM != null && listRRCM.Count > 0)
                    {

                        foreach (var rr in listRRCM)
                        {
                            await CMAutoAssignRoom(rr.ReservationRoomId);
                        }

                        //Check conflic after auto assign

                        //foreach (var rr in listRRCM)
                        //{   
                        //    var listRRAfterAssign = context.ReservationRooms.Where(r => r.ReservationId == rr.ReservationId && r.RoomId > 0).ToList();
                        //    if (listRRAfterAssign != null && listRRAfterAssign.Count > 0)
                        //    {
                        //        foreach (var item in listRRAfterAssign)
                        //        {
                        //            string subject = "";
                        //            string body = "";
                        //            // check conflic, remove room and send email to ezCloud admin
                        //            var conflictReservation = context.ReservationRooms.Where(r => r.HotelId == item.HotelId && r.ReservationRoomId != item.ReservationRoomId
                        //                                        && r.RoomId == item.RoomId 
                        //                                        && r.BookingStatus != "CHECKOUT"
                        //                                        && r.BookingStatus != "CANCELLED"
                        //                                        && r.ArrivalDate < item.DepartureDate 
                        //                                        && item.ArrivalDate <= r.DepartureDate).FirstOrDefault();
                                    
                        //            if (conflictReservation != null)
                        //            {
                                        
                        //                subject = "Error: Conflict with other reservation room while processing auto assign room in hotel:" + item.Hotels.HotelName + "(Hotel ID:" + item.HotelId.ToString() + ")";
                        //                body = "Please check and resolve it. Reservation detail:" +
                        //                                                " Res No: " + item.Reservations.ReservationNumber.ToString() +
                        //                                                ", Room Name: " + item.Rooms.RoomName +
                        //                                                ", Arrival Date: " + item.ArrivalDate.Value.ToString("dd/MM/yyy HH:mm") +
                        //                                                ", Departure Date: " + item.DepartureDate.Value.ToString("dd/MM/yyy HH:mm")+
                        //                                                ", Conflic with  detail: </br>" +
                        //                                                ", Res No: " + conflictReservation.Reservations.ReservationNumber.ToString() +
                        //                                                ", Reservation Room Id: " + conflictReservation.ReservationRoomId.ToString() +
                        //                                                ", Room Name: " + conflictReservation.Rooms.RoomName +
                        //                                                ", Arrival Date: " + conflictReservation.ArrivalDate.Value.ToString("dd/MM/yyy HH:mm") +
                        //                                                ", Departure Date: " + conflictReservation.DepartureDate.Value.ToString("dd/MM/yyy HH:mm");

                        //                EmailSender.SendEmail("dung.nguyen@ezcloud.vn", subject, body);
                        //            }



                        //            var conflictRoomRepair = context.RoomRepair.Where(r => r.HotelId == item.HotelId && r.RoomId == item.RoomId && r.IsDeleted == false
                        //                                    && (r.RepairStartDate <= item.DepartureDate && item.ArrivalDate <= r.RepairEndDate)).FirstOrDefault();
                        //            if (conflictRoomRepair != null)
                        //            {
                        //                subject = "Error: Conflict repair room while auto assign room in hotel:" + item.Hotels.HotelName + "(Hotel ID:" + item.HotelId.ToString() + ")";
                        //                body = "Please check and resolve it. Reservation detail: </br>" +
                        //                                                " Res No: " + item.Reservations.ReservationNumber.ToString() +
                        //                                                ", Room Name: " + item.Rooms.RoomName +
                        //                                                ", Arrival Date: " + item.ArrivalDate.Value.ToString("dd/MM/yyy HH:mm") +
                        //                                                ", Departure Date: " + item.DepartureDate.Value.ToString("dd/MM/yyy HH:mm");

                        //                EmailSender.SendEmail("dung.nguyen@ezcloud.vn", subject, body);
                        //            }


                        //        }
                        //    }


                        //}
                    }
                    //Common.Elastic.IndexLastAudit(context);
                }
                WebApiApplication.flagRunProcessAutoAssignRoom = true;
            }
            catch (Exception ex)
            {
                WebApiApplication.flagRunProcessAutoAssignRoom = true;
                //throw;
            }
            
        }

        public class CombinedHotelFreeTimeBlock
        {
            public List<IEnumerable<ITimePeriod>> validFreeTimeBlockSets { get; set; }

            public Dictionary<int, TimePeriodCollection> roomFreeTimeBlocks { get; set; }
            public Rooms singleRoomValid { get; set; }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="currentHotel"></param>
        /// <param name="ArrivalDate"></param>
        /// <param name="DepartureDate"></param>
        /// <param name="RoomTypeId"></param>
        /// <returns></returns>
        public static CombinedHotelFreeTimeBlock BuildCombinedHotelFreeTimeBlock(Hotels currentHotel, DateTime? ArrivalDate, DateTime? DepartureDate, int? RoomTypeId, List<Rooms> rooms)
        {
            TimePeriodCollection combinedCMRRPeriods = new TimePeriodCollection();
            combinedCMRRPeriods.Add(new TimeRange(ArrivalDate.Value, DepartureDate.Value));
            Rooms singleRoomValid                                    = null;
            //var rooms                                                = currentHotel.RoomsList.Where(rm => rm.RoomTypeId == RoomTypeId).ToList();
            var reservationRooms                                     = currentHotel.ReservationRoomsList.Where(rr => rr.RoomId != null && rr.ArrivalDate.Value < DepartureDate.Value && ArrivalDate.Value < rr.DepartureDate && rr.BookingStatus != "CANCELLED" && rr.BookingStatus != "CHECKOUT").ToList();
            var roomRepairs                                          = currentHotel.RoomRepairList.Where(repair => repair.RepairStartDate < DepartureDate.Value && ArrivalDate.Value < repair.RepairEndDate && repair.IsDeleted == false).ToList();
            TimePeriodCollection combinedHotelFreeTimeBlocks         = new TimePeriodCollection();
            Dictionary<int, TimePeriodCollection> roomFreeTimeBlocks = new Dictionary<int, TimePeriodCollection>();
            TimePeriodCombiner<TimeRange> periodCombiner             = new TimePeriodCombiner<TimeRange>();
            foreach (var room in rooms)
            {
                TimePeriodCollection rrTimeBlock = null;
                var rrTemp = reservationRooms.Where(rr => rr.RoomId == room.RoomId).ToList();
                if (rrTemp != null && rrTemp.Count() > 0)
                {
                    rrTimeBlock = new TimePeriodCollection();
                    foreach (var rr in rrTemp)
                    {
                        rrTimeBlock.Add(new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value));
                    }
                }
                TimePeriodCollection roomRepairTimeBlock = null;
                var repairTemp = roomRepairs.Where(rp => rp.RoomId == room.RoomId).ToList();
                if (repairTemp != null && repairTemp.Count > 0)
                {
                    roomRepairTimeBlock = new TimePeriodCollection();
                    foreach (var repair in repairTemp)
                    {
                        roomRepairTimeBlock.Add(new TimeRange(repair.RepairStartDate.Value, repair.RepairEndDate.Value));
                    }
                }

                if (rrTimeBlock == null && roomRepairTimeBlock == null)
                {
                    singleRoomValid = room;
                    break;
                }

                var combinedUsingTimeBlock = new TimePeriodCollection();
                if (rrTimeBlock != null)
                {
                    combinedUsingTimeBlock.AddAll(rrTimeBlock);
                }

                if (roomRepairTimeBlock != null)
                {
                    combinedUsingTimeBlock.AddAll(roomRepairTimeBlock);
                }

                var gapPeriod = GapFinder(combinedCMRRPeriods, combinedUsingTimeBlock);
                combinedHotelFreeTimeBlocks.AddAll(gapPeriod);
                roomFreeTimeBlocks.Add(room.RoomId, gapPeriod);
            }

            return new CombinedHotelFreeTimeBlock()
            {
                validFreeTimeBlockSets = RebuildFreeTimeBlocks(combinedHotelFreeTimeBlocks.GetSubsets().Where(s => SubsetSum(s) >= ToMilisecond(combinedCMRRPeriods) && s.Any(s1 => s1.Start == ArrivalDate.Value) && s.Any(s1 => s1.End == DepartureDate.Value)).ToList()),
                roomFreeTimeBlocks     = roomFreeTimeBlocks,
                singleRoomValid        = singleRoomValid
            };
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="block"></param>
        /// <param name="room"></param>
        /// <param name="isFirstBlock"></param>
        /// <returns></returns>
        public static TimeInterval MaxLimitOfTimeBlock(ITimePeriod block, Rooms room, bool isFirstBlock)
        {
            TimeInterval result;

            if (isFirstBlock)
            {
                var endTimeRange = block.End;
                var reservationRoomList = room.ReservationRoomsList.Where(rr => rr.DepartureDate <= block.Start);
                if (reservationRoomList != null && reservationRoomList.Count() > 0)
                {
                    var roomRepairList = room.RoomRepairList.Where(rr => rr.RepairEndDate <= block.Start);
                    var rrTemp         = reservationRoomList.OrderByDescending(rr => rr.DepartureDate).FirstOrDefault();
                    var repairTemp     = roomRepairList.OrderByDescending(rr => rr.RepairEndDate).FirstOrDefault();
                    result             = (repairTemp != null && repairTemp.RepairEndDate >= rrTemp.DepartureDate) ? new TimeInterval(repairTemp.RepairEndDate.Value, block.End) : new TimeInterval(rrTemp.DepartureDate.Value, block.End);
                }
                else
                {
                    var minDateTime = (DateTime)SqlDateTime.MinValue;
                    result = new TimeInterval((DateTime)SqlDateTime.MinValue, block.End);
                }
                return result;
            }
            else
            {
                var reservationRoomListLastBlock = room.ReservationRoomsList.Where(rr => rr.ArrivalDate >= block.End);
                if (reservationRoomListLastBlock != null && reservationRoomListLastBlock.Count() > 0)
                {
                    var roomRepairListLastBlock = room.RoomRepairList.Where(rr => rr.RepairStartDate >= block.End);
                    var rrTempLastBlock         = reservationRoomListLastBlock.OrderBy(rr => rr.DepartureDate).FirstOrDefault();
                    var repairTempLastBlock     = roomRepairListLastBlock.OrderBy(rr => rr.RepairEndDate).FirstOrDefault();
                    result                      = (repairTempLastBlock != null && repairTempLastBlock.RepairStartDate <= rrTempLastBlock.ArrivalDate) ? new TimeInterval(block.Start, repairTempLastBlock.RepairStartDate.Value) : new TimeInterval(block.Start, rrTempLastBlock.ArrivalDate.Value);
                }
                else
                {
                    result = new TimeInterval(block.Start, (DateTime)SqlDateTime.MaxValue);
                }

                return result;
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="rooms"></param>
        /// <param name="roomId"></param>
        /// <param name="ArrivalDate"></param>
        /// <param name="DepartureDate"></param>
        /// <returns></returns>
        public static bool CheckConflict(List<Rooms> rooms, int roomId, DateTime? ArrivalDate, DateTime? DepartureDate)
        {
            var result = true;
            var room = rooms.FirstOrDefault(rm => rm.RoomId == roomId);
            if (room != null)
            {
                var conflictRoomRepair = room.RoomRepairList.Where(rr => rr.IsDeleted == false && (rr.RepairStartDate <= DepartureDate && ArrivalDate <= rr.RepairEndDate)).FirstOrDefault();
                if (conflictRoomRepair != null)
                {
                    result = false;
                    return result;
                }

                var conflictRR = room.ReservationRoomsList.Where(rr => rr.BookingStatus != "CHECKOUT" && rr.BookingStatus != "CANCELLED" && (rr.ArrivalDate <= DepartureDate && ArrivalDate <= rr.DepartureDate)).FirstOrDefault();
                if (conflictRR != null)
                {
                    result = false;
                    return result;
                }
            }
            return result;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="firstBlock"></param>
        /// <param name="lastBlock"></param>
        /// <param name="roomFreeTimeBlocks"></param>
        /// <param name="rooms"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public static Task<bool> SwapReservationRoomsToFreeTimeBlock(ITimePeriod firstBlock, ITimePeriod lastBlock, Dictionary<int, TimePeriodCollection> roomFreeTimeBlocks, List<Rooms> rooms, ReservationRooms RR, ezCloudDataContext context)
        {
            var result = false;
            var flagRoomRepairFound_FirstBlock = false;
            Rooms roomFlagRoomRepairFound_FirstBlock = null;
            var flagRoomRepairFound_LastBlock = false;
            Rooms roomFlagRoomRepairFound_LastBlock = null;
            var foundInFirstBlock = false;
            var roomsHasFirstBlock = roomFreeTimeBlocks.Where(rd => rd.Value.Any(rdv => rdv.HasInside(firstBlock)));
            var roomsHasLastBlock = roomFreeTimeBlocks.Where(rd => rd.Value.Any(rdv => rdv.HasInside(lastBlock)));
            if (RR != null)
            {
                foreach (var roomTemp in roomsHasFirstBlock)
                {
                    var room = rooms.FirstOrDefault(r => r.RoomId == roomTemp.Key);
                    foreach (var room2 in roomsHasLastBlock)
                    {
                        var roomTempLastBlock = rooms.FirstOrDefault(r => r.RoomId == room2.Key);
                        var timeRangeCheck    = new TimeRange(firstBlock.End.AddMilliseconds(-1), MaxLimitOfTimeBlock(lastBlock, roomTempLastBlock, false).End.AddMilliseconds(1));
                        if (flagRoomRepairFound_FirstBlock == false && room.RoomRepairList.Any(rr => new TimeRange(rr.RepairStartDate.Value, rr.RepairEndDate.Value).GetIntersection(timeRangeCheck) != null))
                        {
                            flagRoomRepairFound_FirstBlock = true;
                            roomFlagRoomRepairFound_FirstBlock = room;
                            continue;
                        }
                        if (room.ReservationRoomsList.Any(rr => rr.BookingStatus == "BOOKED" && new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value).GetRelation(timeRangeCheck) == PeriodRelation.StartInside) || room.ReservationRoomsList.Any(rr => new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value).HasInside(timeRangeCheck) == true) )
                        {
                            continue;
                        }
                        else
                        {
                            var validRRInTimeRangeCheck = room.ReservationRoomsList.Where(rr => rr.BookingStatus == "BOOKED" && timeRangeCheck.HasInside(new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value))).ToList();
                            if (validRRInTimeRangeCheck == null)
                            {
                                continue;
                            }
                            else
                            {
                                foreach (var rr in validRRInTimeRangeCheck)
                                {
                                    foreach (var rm in roomsHasLastBlock)
                                    {
                                        if (CheckConflict(rooms, rm.Key, rr.ArrivalDate.Value.AddMilliseconds(-1), rr.DepartureDate.Value.AddMilliseconds(1)))
                                        {
                                            result = true;
                                            foundInFirstBlock = true;
                                            rr.RoomId = rm.Key;
                                            rr.ArrivalDate.Value.AddSeconds(1);
                                            rr.DepartureDate.Value.AddSeconds(-1);
                                            break;
                                        }
                                        else
                                        {
                                            continue;
                                        }
                                    }
                                }
                            }
                        }
                        if (result == true)
                        {
                            RR.RoomId = room.RoomId;
                            break;
                        }
                    }
                    if (result == true)
                    {
                        break;
                    }
                }

                if (!foundInFirstBlock)
                {
                    foreach (var roomTemp in roomsHasLastBlock)
                    {
                        var room = rooms.FirstOrDefault(r => r.RoomId == roomTemp.Key);
                        foreach (var room1 in roomsHasFirstBlock)
                        {
                            var roomTempFirstBlock = rooms.FirstOrDefault(r => r.RoomId == room1.Key);
                            var timeRangeCheck = new TimeRange(MaxLimitOfTimeBlock(firstBlock, roomTempFirstBlock, true).Start.AddMilliseconds(-1), lastBlock.Start.AddMilliseconds(1));
                            if (flagRoomRepairFound_LastBlock == false && room.RoomRepairList.Any(rr => new TimeRange(rr.RepairStartDate.Value, rr.RepairEndDate.Value).GetIntersection(timeRangeCheck) != null))
                            {
                                flagRoomRepairFound_LastBlock = true;
                                roomFlagRoomRepairFound_LastBlock = room;
                                continue;
                            }
                            if (room.ReservationRoomsList.Any(rr => rr.BookingStatus == "BOOKED" && new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value).GetRelation(timeRangeCheck) == PeriodRelation.EndInside) || room.ReservationRoomsList.Any(rr => new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value).HasInside(timeRangeCheck) == true))
                            {
                                continue;
                            }
                            else
                            {
                                var validRRInTimeRangeCheck = room.ReservationRoomsList.Where(rr => rr.BookingStatus == "BOOKED" && timeRangeCheck.HasInside(new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value))).ToList();
                                if (validRRInTimeRangeCheck == null)
                                {
                                    continue;
                                }
                                else
                                {
                                    var defaultRoomInFirstBlock = roomsHasFirstBlock.FirstOrDefault();
                                    foreach (var rr in validRRInTimeRangeCheck)
                                    {
                                        foreach (var rm in roomsHasFirstBlock)
                                        {
                                            if (CheckConflict(rooms, rm.Key, rr.ArrivalDate.Value.AddMilliseconds(-1), rr.DepartureDate.Value.AddMilliseconds(1)))
                                            {
                                                result = true;
                                                rr.ArrivalDate.Value.AddSeconds(1);
                                                rr.DepartureDate.Value.AddSeconds(-1);
                                                rr.RoomId = rm.Key;
                                                break;
                                            }
                                            else
                                            {
                                                continue;
                                            }
                                        }
                                    }
                                }
                            }
                            if (result == true)
                            {
                                RR.RoomId = room.RoomId;
                                break;
                            }
                        }

                        if (result == true)
                        {
                            break;
                        }
                    }
                }
                if (result == false)
                {
                    if (flagRoomRepairFound_FirstBlock == true && roomFlagRoomRepairFound_FirstBlock != null)
                    {
                        foreach(var roomLastBlockTemp in roomsHasLastBlock)
                        {
                            if (roomLastBlockTemp.Key != 0 && roomLastBlockTemp.Value != null)
                            {
                                result = true;
                                RR.RoomId = roomFlagRoomRepairFound_FirstBlock.RoomId;
                                RR.ArrivalDate = firstBlock.Start.AddSeconds(1);
                                RR.DepartureDate = firstBlock.End.AddSeconds(-1);
                                RR.Reservations.IsGroup = true;
                                if (RR.Reservations.IsGroup == false)
                                {
                                    RR.IsGroupMaster = true;
                                }

                                RR.Reservations.IsGroup = true;

                                var newRR = CopyRRFrom(RR);
                                newRR.ReservationRoomId = 0;
                                newRR.RoomId = roomLastBlockTemp.Key;
                                newRR.ArrivalDate = firstBlock.End.AddSeconds(1);
                                newRR.DepartureDate = lastBlock.End.AddSeconds(-1);
                                newRR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber((int)RR.HotelId, "RESERVATIONROOM");
                                newRR.IsGroupMaster = false;

                                context.ReservationRooms.InsertOnSubmit(newRR);
                                break;
                            }
                        }
                        
                    }
                }

                if (result == false)
                {
                    if (flagRoomRepairFound_LastBlock == true && roomFlagRoomRepairFound_LastBlock != null)
                    {
                        foreach(var roomFirstBlockTemp in roomsHasFirstBlock)
                        {
                            if (roomFirstBlockTemp.Key != 0 && roomFirstBlockTemp.Value != null)
                            {
                                result = true;
                                RR.RoomId = roomFlagRoomRepairFound_LastBlock.RoomId;
                                RR.ArrivalDate = lastBlock.Start.AddSeconds(1);
                                RR.DepartureDate = lastBlock.End.AddSeconds(-1);

                                var newRR = CopyRRFrom(RR);
                                newRR.ReservationRoomId = 0;
                                newRR.RoomId = roomFirstBlockTemp.Key;
                                newRR.ArrivalDate = firstBlock.Start.AddSeconds(1);
                                newRR.DepartureDate = lastBlock.Start.AddSeconds(-1);
                                newRR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber((int)RR.HotelId, "RESERVATIONROOM");

                                context.ReservationRooms.InsertOnSubmit(newRR);
                                break;
                            }
                        }


                        
                    }
                }
            }

            return Task.FromResult(result);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="srcRR"></param>
        /// <returns></returns>
        public static ReservationRooms CopyRRFrom(ReservationRooms srcRR)
        {
            var destRR                  = new ReservationRooms();
            destRR.TravellerId          = srcRR.TravellerId;
            destRR.HotelId              = srcRR.HotelId;
            destRR.Note                 = srcRR.Note;
            destRR.ReservationId        = srcRR.ReservationId;
            destRR.Adults               = srcRR.Adults;
            destRR.Child                = srcRR.Child;
            destRR.BookingStatus        = srcRR.BookingStatus;
            destRR.Price                = srcRR.Price;
            destRR.Foc                  = srcRR.Foc;
            destRR.RoomPriceId          = srcRR.RoomPriceId;
            destRR.DiscountPercentage   = srcRR.DiscountPercentage;
            destRR.DiscountFlat         = srcRR.DiscountFlat;
            destRR.RoomTypeId           = srcRR.RoomTypeId;
            destRR.CreatedDate          = srcRR.CreatedDate;
            destRR.CreatedUserId        = srcRR.CreatedUserId;
            destRR.CMConfirmed          = srcRR.CMConfirmed;
            destRR.CMErrorDescription   = srcRR.CMErrorDescription;
            destRR.CMReceiveBookingDate = srcRR.CMReceiveBookingDate;
            return destRR;
        }

        public static EntitySet<T> ToEntitySet<T>(this IEnumerable<T> source) where T : class
        {
            var es = new EntitySet<T>();
            es.AddRange(source);
            return es;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="RR"></param>
        /// <param name="firstBlock"></param>
        /// <param name="lastBlock"></param>
        /// <param name="roomFreeTimeBlocks"></param>
        /// <param name="second_combinedHotelFreeTimeBlock"></param>
        /// <param name="rooms"></param>
        /// <param name="context"></param>
        /// <returns></returns>
        public static async Task<bool> SplitReservationRooms(ReservationRooms RR, ITimePeriod firstBlock, ITimePeriod lastBlock, Dictionary<int, TimePeriodCollection> roomFreeTimeBlocks, CombinedHotelFreeTimeBlock second_combinedHotelFreeTimeBlock, List<Rooms> rooms, ezCloudDataContext context)
        {
            var roomsHasFirstBlock = roomFreeTimeBlocks.Where(rd => rd.Value.Any(rdv => rdv.HasInside(firstBlock))).FirstOrDefault();
            var roomsHasLastBlock  = roomFreeTimeBlocks.Where(rd => rd.Value.Any(rdv => rdv.HasInside(lastBlock))).FirstOrDefault();
            var roomsHasBoth       = roomFreeTimeBlocks.Where(rd => rd.Value.Any(rdv => rdv.HasInside(firstBlock)) && rd.Value.Any(rdv => rdv.HasInside(lastBlock))).ToList();
            var roomCharges        = RR.RoomChargesList.ToList().ConvertAll(rc => rc);
            var roomBreakfasts     = RR.RoomBreakfastList.ToList().ConvertAll(rb => rb);
            if (second_combinedHotelFreeTimeBlock.singleRoomValid != null)
            {
                if (roomsHasBoth != null && roomsHasBoth.Count > 0)
                {
                    foreach (var both in roomsHasBoth)
                    {
                        var roomBoth      = rooms.FirstOrDefault(r => r.RoomId == both.Key);
                        var bothTimeRange = new TimeRange(firstBlock.End, lastBlock.Start);
                        var bothCount     = roomBoth.ReservationRoomsList.Where(rr => bothTimeRange.HasInside(new TimeRange(rr.ArrivalDate.Value, rr.DepartureDate.Value)));
                        if (bothCount.Count() == 1)
                        {
                            var roomTemp = rooms.Where(rm => rm.RoomId != both.Key && CheckConflict(rooms, rm.RoomId, firstBlock.End.AddSeconds(1), lastBlock.Start.AddSeconds(-1))).FirstOrDefault();
                            if (roomTemp != null)
                            {
                                bothCount.FirstOrDefault().RoomId = roomTemp.RoomId;
                                RR.RoomId = both.Key;
                                RR.ArrivalDate.Value.AddSeconds(1);
                                RR.DepartureDate.Value.AddSeconds(-1);
                                context.SubmitChanges();
                                return true;
                            }
                        }
                    }
                }

                var reservationRoomList = new List<ReservationRooms>();
                if (RR.Reservations != null)
                {
                    RR.ArrivalDate   = firstBlock.Start.AddSeconds(1);
                    RR.DepartureDate = firstBlock.End.AddSeconds(-1);
                    RR.RoomId        = roomsHasFirstBlock.Key;

                    var singleRoomValid_RR                   = CopyRRFrom(RR);
                    singleRoomValid_RR.ReservationRoomId     = 0;
                    singleRoomValid_RR.ArrivalDate           = firstBlock.End.AddSeconds(1);
                    singleRoomValid_RR.DepartureDate         = lastBlock.Start.AddSeconds(-1);
                    singleRoomValid_RR.RoomId                = second_combinedHotelFreeTimeBlock.singleRoomValid.RoomId;
                    singleRoomValid_RR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber((int)RR.HotelId, "RESERVATIONROOM");

                    var lastBlock_RR                   = new ReservationRooms();
                    lastBlock_RR                       = CopyRRFrom(RR);
                    lastBlock_RR.ReservationRoomId     = 0;
                    lastBlock_RR.ArrivalDate           = lastBlock.Start.AddSeconds(1);
                    lastBlock_RR.DepartureDate         = lastBlock.End.AddSeconds(-1);
                    lastBlock_RR.RoomId                = roomsHasLastBlock.Key;
                    lastBlock_RR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber((int)RR.HotelId, "RESERVATIONROOM");

                    if (RR.Reservations.IsGroup == false)
                    {
                        RR.IsGroupMaster = true;
                    }

                    RR.Reservations.IsGroup = true;

                    singleRoomValid_RR.IsGroupMaster = false;
                    lastBlock_RR.IsGroupMaster       = false;

                    reservationRoomList.Add(RR);
                    reservationRoomList.Add(singleRoomValid_RR);
                    reservationRoomList.Add(lastBlock_RR);

                    context.ReservationRooms.InsertOnSubmit(singleRoomValid_RR);
                    context.ReservationRooms.InsertOnSubmit(lastBlock_RR);
                    context.SubmitChanges();

                    await ReservationUtility.RebuildRoomChargesAndBreakfastCM(reservationRoomList,roomCharges, roomBreakfasts, context);
                    return true;
                }
            }

            var second_validFreeTimeBlockSets = second_combinedHotelFreeTimeBlock.validFreeTimeBlockSets;
            var second_roomFreeTimeBlocks     = second_combinedHotelFreeTimeBlock.roomFreeTimeBlocks;
            
            if (second_validFreeTimeBlockSets != null && second_validFreeTimeBlockSets.Count > 0)
            {
                var second_validFreeTimeBlockSets_Temp = second_validFreeTimeBlockSets.ConvertAll(set => set);
                foreach (var set in second_validFreeTimeBlockSets)
                {
                    var second_firstBlock = set.FirstOrDefault(s => s.Start == firstBlock.End);
                    var second_lastBlock = set.FirstOrDefault(s => s.End == lastBlock.Start);
                    var second_roomsHasFirstBlock = second_roomFreeTimeBlocks.Where(rd => rd.Value.Any(rdv => rdv.HasInside(second_firstBlock))).FirstOrDefault();
                    var second_roomsHasLastBlock = second_roomFreeTimeBlocks.Where(rd => rd.Value.Any(rdv => rdv.HasInside(second_lastBlock))).FirstOrDefault();
                    if (second_firstBlock.IntersectsWith(second_lastBlock))
                    {
                        var reservationRoomList = new List<ReservationRooms>();
                        if (RR.Reservations != null)
                        {
                            RR.ArrivalDate   = firstBlock.Start.AddSeconds(1);
                            RR.DepartureDate = firstBlock.End.AddSeconds(-1);
                            RR.RoomId        = roomsHasFirstBlock.Key;

                            var lastBlock_RR                   = CopyRRFrom(RR);
                            lastBlock_RR.ReservationRoomId     = 0;
                            lastBlock_RR.ArrivalDate           = lastBlock.Start.AddSeconds(1);
                            lastBlock_RR.DepartureDate         = lastBlock.End.AddSeconds(-1);
                            lastBlock_RR.RoomId                = roomsHasLastBlock.Key;
                            lastBlock_RR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber((int)RR.HotelId, "RESERVATIONROOM");

                            var second_firstBlock_RR                   = CopyRRFrom(RR);
                            second_firstBlock_RR.ReservationRoomId     = 0;
                            second_firstBlock_RR.ArrivalDate           = second_firstBlock.Start.AddSeconds(1);
                            second_firstBlock_RR.DepartureDate         = second_firstBlock.End.AddSeconds(-1);
                            second_firstBlock_RR.RoomId                = second_roomsHasFirstBlock.Key;
                            second_firstBlock_RR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber((int)RR.HotelId, "RESERVATIONROOM");

                            var second_lastBlock_RR                   = CopyRRFrom(RR);
                            second_lastBlock_RR.ReservationRoomId     = 0;
                            second_lastBlock_RR.ArrivalDate           = second_firstBlock.End.AddSeconds(1);
                            second_lastBlock_RR.DepartureDate         = second_lastBlock.End.AddSeconds(-1);
                            second_lastBlock_RR.RoomId                = second_roomsHasLastBlock.Key;
                            second_lastBlock_RR.ReservationRoomNumber = InvoiceNumberGenerator.getInvoiceNumber((int)RR.HotelId, "RESERVATIONROOM");

                            if (RR.Reservations.IsGroup == false)
                            {
                                RR.IsGroupMaster = true;
                            }

                            RR.Reservations.IsGroup = true;

                            

                            context.ReservationRooms.InsertOnSubmit(second_firstBlock_RR);
                            context.ReservationRooms.InsertOnSubmit(second_lastBlock_RR);
                            context.ReservationRooms.InsertOnSubmit(lastBlock_RR);
                            context.SubmitChanges();

                            reservationRoomList.Add(RR);
                            reservationRoomList.Add(lastBlock_RR);
                            reservationRoomList.Add(second_firstBlock_RR);
                            reservationRoomList.Add(second_lastBlock_RR);

                            await ReservationUtility.RebuildRoomChargesAndBreakfastCM(reservationRoomList, roomCharges,roomBreakfasts, context);
                            return true;
                        }
                    }
                    else
                    {
                        continue;
                    }
                }
            }

            return false;
            //}
        }

        public static TimeRange FindMaxRangeFreeTimeBlock(Rooms room, ITimePeriod elem, bool isFirstElem)
        {
            TimeRange result = new TimeRange();
            if (isFirstElem)
            {
                result.Start = elem.End;
                var nearestRoomRepair = room.RoomRepairList.Where(rr => rr.RepairStartDate >= elem.End).OrderBy(rr => rr.RepairStartDate).FirstOrDefault();
                var nearestReservationRoom = room.ReservationRoomsList.Where(rr => rr.ArrivalDate >= elem.End).OrderBy(rr => rr.ArrivalDate).FirstOrDefault();
                var entryPoint = nearestRoomRepair.RepairStartDate <= nearestReservationRoom.ArrivalDate ? nearestRoomRepair.RepairStartDate : nearestReservationRoom.ArrivalDate;
                result.End = entryPoint.Value;
            }
            else
            {
                result.End = elem.Start;
                var mostlyRoomRepair = room.RoomRepairList.Where(rr => rr.RepairEndDate <= elem.Start).OrderByDescending(rr => rr.RepairEndDate).FirstOrDefault();
                var mostlyReservationRoom = room.ReservationRoomsList.Where(rr => rr.DepartureDate <= elem.End).OrderByDescending(rr => rr.DepartureDate).FirstOrDefault();
                var entryPoint = mostlyRoomRepair.RepairEndDate >= mostlyReservationRoom.DepartureDate ? mostlyRoomRepair.RepairEndDate : mostlyReservationRoom.DepartureDate;
                result.Start = entryPoint.Value;
            }

            return result;
        }

        public static IEnumerable<ITimePeriod> NormalizeFreeTimeBlockSet(IEnumerable<ITimePeriod> set, ITimePeriodCollection period)
        {
            var firstElem = set.FirstOrDefault(s => s.Start == period.Start);
            var lastElem = set.FirstOrDefault(s => s.End == period.End);

            return null;
        }

        public class FreeTimeBlockEntryPointResult
        {
            public bool IsSuccess { get; set; }
            public TimePeriodCollection Result { get; set; }
        }

        public static FreeTimeBlockEntryPointResult FindFreeTimeBlockEntryPoint(TimePeriodCollection periods, TimePeriodCollection searchLimits)
        {
            FreeTimeBlockEntryPointResult processingResult = new FreeTimeBlockEntryPointResult()
            {
                IsSuccess = false,
                Result = new TimePeriodCollection()
            };
            var searchLimitsTimeRange = new CalendarTimeRange(searchLimits.Start, searchLimits.End);
            var startFreeTimeBlock = FindLargestFreeTimeBlock("START", periods, searchLimits);
            if (startFreeTimeBlock != null)
            {
                processingResult.Result.Add(startFreeTimeBlock);
            }
            if (startFreeTimeBlock == searchLimitsTimeRange)
            {
                processingResult.IsSuccess = true;
                return processingResult;
            }
            var endFreeTimeBlock = FindLargestFreeTimeBlock("END", periods, searchLimits);
            if (endFreeTimeBlock != null)
            {
                processingResult.Result.Add(endFreeTimeBlock);
            }

            if (IsTimePeriodIntersections(processingResult.Result))
            {
                processingResult.IsSuccess = true;
                return processingResult;
            }
            return processingResult;
        }

        public static bool IsValidTimeRange(TimeRange workingHours, ITimePeriod period)
        {
            return workingHours.HasInside(period);
        }

        public static bool IsValidFreeTimeBlocks(ICalendarTimeRange first, ICalendarTimeRange second)
        {
            return first.HasInside(second) || second.HasInside(first);
        }

        public static ITimePeriodCollection FindTimePeriodIntersections(TimePeriodCollection periods)
        {
            TimePeriodIntersector<TimeRange> periodIntersector = new TimePeriodIntersector<TimeRange>();
            ITimePeriodCollection intersectedPeriods = periodIntersector.IntersectPeriods(periods);
            return intersectedPeriods;
        }

        public static bool IsTimePeriodIntersections(TimePeriodCollection periods)
        {
            return FindTimePeriodIntersections(periods) != null;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="timeBlocks"></param>
        /// <returns></returns>
        public static List<IEnumerable<ITimePeriod>> RebuildFreeTimeBlocks(List<IEnumerable<ITimePeriod>> timeBlocks)
        {
            var timeBlocksTemp = timeBlocks.ConvertAll(m => m);
            foreach (var period in timeBlocksTemp)
            {
                var periodTemp = period.ToArray();
                for (int i = 0; i < periodTemp.Count() - 1; i++)
                {
                    var isValid = true;
                    for (int j = i + 1; j < periodTemp.Count(); j++)
                    {
                        var timeRange_i = new TimeRange(periodTemp[i].Start, periodTemp[i].End);
                        var timeRange_j = new TimeRange(periodTemp[j].Start, periodTemp[j].End);
                        if (IsValidTimeRange(timeRange_i, periodTemp[j]) || IsValidTimeRange(timeRange_j, periodTemp[i]))
                        {
                            isValid = false;
                            timeBlocks.Remove(period);
                        }
                    }
                    if (!isValid)
                    {
                        continue;
                    }
                }
            }
            return timeBlocks;
        }

        public static long SubsetSum(IEnumerable<ITimePeriod> period)
        {
            long sum = 0;
            foreach (var p in period)
            {
                sum += ToMilisecond(p);
            }
            return sum;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="basePeriods"></param>
        /// <param name="testPeriods"></param>
        /// <returns></returns>
        public static TimePeriodCollection GapFinder(TimePeriodCollection basePeriods, TimePeriodCollection testPeriods)
        {
            // base periods
            ITimePeriodCollection combinedCMRRPeriods = new TimePeriodCombiner<TimeRange>().CombinePeriods(basePeriods);

            // test periods
            ITimePeriodCollection combinedHotelRRPeriods = new TimePeriodCombiner<TimeRange>().CombinePeriods(testPeriods);

            // gaps
            TimePeriodCollection gaps = new TimePeriodCollection();
            foreach (ITimePeriod basePeriod in combinedCMRRPeriods)
            {
                gaps.AddAll(new TimeGapCalculator<TimeRange>().GetGaps(combinedHotelRRPeriods, basePeriod));
            }
            return gaps;
        }

        public static List<long> minSubArray(List<long> arrA, long x)
        {
            int start = 0;
            int ansEnd = 0;
            int ansStart = 0;
            long currSum = 0;
            int minLen = arrA.Count();
            for (int i = 0; i <= arrA.Count(); i++)
            {
                while (currSum > x)
                {
                    currSum = currSum - arrA[start];
                    if (i - start < minLen)
                    {
                        minLen = (i - start);
                        ansEnd = i;
                        ansStart = start;
                    }
                    start++;
                }
                if (i < arrA.Count())
                {
                    currSum = currSum + arrA[i];
                }
            }
            return arrA;
        }

        public static long ToMilisecond(ITimePeriod gap)
        {
            return (long)(gap.End - gap.Start).TotalMilliseconds;
        }

        /// <summary>
        ///
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <returns></returns>
        public static IEnumerable<IEnumerable<T>> GetSubsets<T>(this IEnumerable<T> collection)
        {
            if (!collection.Any())
            {
                return Enumerable.Repeat(Enumerable.Empty<T>(), 1);
            }
            var element = collection.Take(1);
            var ignoreFirstSubsets = GetSubsets(collection.Skip(1));
            var subsets = ignoreFirstSubsets.Select(set => element.Concat(set));
            var result = subsets.Concat(ignoreFirstSubsets);
            return result;
        }

        public static ITimePeriodCollection FindAllFreeTimeBlock(IEnumerable<ITimePeriod> reservations, ITimePeriod searchLimits = null, bool excludeWeekends = false)
        {
            TimePeriodCollection bookedPeriods = new TimePeriodCollection(reservations);
            if (searchLimits == null)
            {
                searchLimits = bookedPeriods; // use boundary of reservations
            }
            if (excludeWeekends)
            {
                Week currentWeek = new Week(searchLimits.Start);
                Week lastWeek = new Week(searchLimits.End);
                do
                {
                    ITimePeriodCollection days = currentWeek.GetDays();
                    foreach (Day day in days)
                    {
                        if (!searchLimits.HasInside(day))
                        {
                            continue; // outside of the search scope
                        }
                        if (day.DayOfWeek == DayOfWeek.Saturday ||
                             day.DayOfWeek == DayOfWeek.Sunday)
                        {
                            bookedPeriods.Add(day); // // exclude weekend day
                        }
                    }
                    currentWeek = currentWeek.GetNextWeek();
                } while (currentWeek.Start < lastWeek.Start);
            }
            // calculate the gaps using the time calendar as period mapper
            TimeGapCalculator<TimeRange> gapCalculator =
              new TimeGapCalculator<TimeRange>(new TimeCalendar());
            ITimePeriodCollection freeTimes =
              gapCalculator.GetGaps(bookedPeriods, searchLimits);
            if (freeTimes.Count == 0)
            {
                return null;
            }
            freeTimes.SortByDuration(); // move the largest gap to the start
            return new TimePeriodCollection(freeTimes.Where(ft => ft.Start == searchLimits.Start || ft.End == searchLimits.End));
        } // FindLargestFreeTimeBlock

        public static ICalendarTimeRange FindLargestFreeTimeBlock(string entryPoint, TimePeriodCollection combinedHotel, ITimePeriod searchLimits = null, bool excludeWeekends = false)
        {
            //TimePeriodCollection bookedPeriods = new TimePeriodCollection(reservations);
            TimePeriodCollection combinedHotelEntry = new TimePeriodCollection();
            switch (entryPoint)
            {
                case "START":
                    var startTemp = combinedHotel.Where(c => c.Start == searchLimits.Start);
                    combinedHotelEntry.AddAll(startTemp);
                    break;

                case "END":
                    var endTemp = combinedHotel.Where(c => c.End == searchLimits.End);
                    combinedHotelEntry.AddAll(endTemp);
                    break;
            }
            if (searchLimits == null)
            {
                searchLimits = combinedHotel; // use boundary of reservations
            }
            if (excludeWeekends)
            {
                Week currentWeek = new Week(searchLimits.Start);
                Week lastWeek = new Week(searchLimits.End);
                do
                {
                    ITimePeriodCollection days = currentWeek.GetDays();
                    foreach (Day day in days)
                    {
                        if (!searchLimits.HasInside(day))
                        {
                            continue; // outside of the search scope
                        }
                        if (day.DayOfWeek == DayOfWeek.Saturday ||
                             day.DayOfWeek == DayOfWeek.Sunday)
                        {
                            combinedHotel.Add(day); // // exclude weekend day
                        }
                    }
                    currentWeek = currentWeek.GetNextWeek();
                } while (currentWeek.Start < lastWeek.Start);
            }
            // calculate the gaps using the time calendar as period mapper
            TimeGapCalculator<TimeRange> gapCalculator = new TimeGapCalculator<TimeRange>(new TimeCalendar());
            ITimePeriodCollection freeTimes = gapCalculator.GetGaps(combinedHotelEntry, searchLimits);
            if (freeTimes.Count == 0)
            {
                return null;
            }
            freeTimes.SortByDuration(); // move the largest gap to the start

            return new CalendarTimeRange(freeTimes[0]);
        } // FindLargestFreeTimeBlock
    }
}